/* 前端项目检测命令主文件 */
const chalk = require('chalk');
const ora = require('ora');
const download = require('download-git-repo');
const clearConsole = require('@lib/utils/clearConsole');
const {
  isValidURL,
  isValidStr,
  isPositiveNum,
  extractDirNameByGitUrl,
  isFePrj,
  npmSetRegistrySync,
  npmInstallSync
} = require('@lib/utils/tools');
const CONSTS = require('@lib/consts/index');
const Fs = require('@lib/utils/fs');
const Strategy = require('./strategy');
const Report = require('@lib/utils/report');

// 项目检测执行类
class InspectExe {
  /**
   * 构造函数
   * @param {*} params 示例：
   * {
   *   prjGitUrl: 'https://xxx',
   *   branchName: 'xxx',
   *   picLimit: 100
   * }
   */
  constructor(params = {}) {
    // 项目git地址
    this._prjGitUrl = '';
    // 项目git分支
    this._branchName = '';
    // 图片大小限值
    this._picLimit = CONSTS.DEFAULT_PICTURE_LIMIT;
    // 默认的指定需要检测的前端项目下的一级目录或者一级文件
    this._dirsFiles = CONSTS.DEFAULT_DIRS_FILES;
    // 统一管理spinner loading标记
    this._spinner = null;
    this._init(params);
  }

  // 初始化方法
  async _init(params) {
    this.validParams(params);
    this._prjGitUrl = params.prjGitUrl;
    this._branchName = params.branchName;
    if (isValidStr(params.picLimit)) {
      this._picLimit = +params.picLimit;
    }
    if (isValidStr(params.dirsFiles)) {
      this._dirsFiles = params.dirsFiles.split('^');
    }
    console.log(this._prjGitUrl, this._branchName, this._picLimit, this._dirsFiles);

    // 这里需要将fe-perf-doctor package.json文件下的npm依赖安装到目标项目下（也就是执行doctor xxx指令的项目），方便执行指令时可以找到安装包
    // 1.先判断当前目录下是否存在package.json文件，存在默认说明执行过npm install安装依赖了
    const fileName = 'package.json';
    const pkgJsonData = {
      name: 'demo-package',
      version: '1.0.0',
      dependencies: {}
    };
    const npmRegistry = 'https://registry.npmmirror.com/';
    const isPkgJsonExist = Fs.isFileExistsInCurDir(fileName);
    // console.log(`package.json文件是否存在: ${isPkgJsonExist}`);
    // 不存在package.json文件，需要创建package.json文件并执行npm install安装依赖
    if (!isPkgJsonExist) {
      console.log('不存在package.json文件，需要创建');
      const { pkgJsonDep } = params;
      pkgJsonData.dependencies = pkgJsonDep;
      const pkgJsonDataStr = JSON.stringify(pkgJsonData);
      // console.log(pkgJsonDataStr);
      // 2.创建package.json文件
      const isPkgJsonSuccess = await Fs.writeData2FileSync(fileName, pkgJsonDataStr);
      if (!isPkgJsonSuccess) {
        throw new Error('写入package.json文件失败');
      }
      // 3.设置npm淘宝镜像源
      const isNpmSetRegistrySuccess = npmSetRegistrySync(npmRegistry);
      if (!isNpmSetRegistrySuccess) {
        throw new Error('设置npm淘宝镜像源失败');
      }
      // 4.执行npm install
      const isNpmInstallSuccess = npmInstallSync();
      if (!isNpmInstallSuccess) {
        throw new Error('npm install安装依赖失败');
      }
    }
  }

  // 获取项目目录路径
  getPrjDir() {
    return `${CONSTS.DEFAULT_PRJ_DIR}/${extractDirNameByGitUrl(this._prjGitUrl)}_${this._branchName}`;
  }

  // 校验入参
  validParams(params) {
    const { prjGitUrl, branchName, picLimit } = params;
    if (!isValidURL(prjGitUrl)) {
      throw new Error('项目git地址非法');
    }
    if (!isValidStr(branchName)) {
      throw new Error('项目git分支不存在');
    }
    if (isValidStr(picLimit) && !isPositiveNum(picLimit)) {
      throw new Error('图片限值非法');
    }
  }

  // 下载项目
  downloadPrj(dir, cb = null) {
    const url = `direct:${this._prjGitUrl}#${this._branchName}`;
    console.log(chalk.white('\n开始拉取项目...\n'));
    this._spinner = ora(`下载项目${url}到目录${dir}下...\n`);
    // 出现加载图标
    this._spinner.start();
    // 执行下载操作
    download(
      url,
      dir,
      { clone: true },
      err => {
        if (err) {
          this._spinner.fail();
          console.log(chalk.red(`拉取模板出错：${err}`));
          throw err;
        }
        // 拉取成功，删除除src目录外的其他目录和文件
        Fs.deleteDirsAndFilesNotInList(dir, this._dirsFiles);
        console.log(chalk.green(`\n已删掉除了${this._dirsFiles.join(';')}外的其他目录和文件成功\n`));
        // 结束加载图标
        this._spinner.succeed();
        console.log(chalk.green('\n拉取模板成功\n'));
        cb && cb();
      }
    );
  }

  // 分析项目性能问题
  analysePrj(prjDir) {
    // 1.遍历目录下全部文件
    this._spinner = ora(`开始提取${prjDir}项目下的文件\n`);
    this._spinner.start();
    const fileList = [];
    Fs.traverseDirectory(prjDir, fileList);
    // console.log(fileList);
    // 2.判断是否是前端项目
    const isFePrjFlag = isFePrj(fileList.map(item => item.fileName));
    if (!isFePrjFlag) {
      throw new Error('该项目非前端项目，请核查');
    }
    // 3.文件按扩展名归类
    const [imgList, htmlList, cssList, jsList, tsList, jsonList, vueList, reactList] = this.classifyFileByExt(fileList);
    this._spinner = ora(`完成提取${prjDir}项目下的文件\n`);
    this._spinner.succeed();
    // 4.将归类好的文件传给检测策略进行检测处理
    this._spinner = ora(`开始分析${prjDir}项目\n`);
    this._spinner.start();
    const strategy = new Strategy({
      img: imgList,
      html: htmlList,
      css: cssList,
      js: jsList,
      ts: tsList,
      json: jsonList,
      vue: vueList,
      react: reactList
    });
    strategy.execute({
      picLimit: this._picLimit
    }, () => {
      this._spinner = ora(`完成分析${prjDir}项目\n`);
      this._spinner.succeed();
      // 5.临时文件整合成html格式的报告并展示
      this._spinner = ora('开始输出源码性能检测报告\n');
      this._spinner.start();
      Report.showReport(`${prjDir}项目源码性能检测报告`, () => {
        this._spinner = ora('完成输出源码性能检测报告\n');
        this._spinner.succeed();
        process.exit(0);
      });
    });
  }

  // 文件按扩展名归类
  classifyFileByExt(fileList) {
    const ret = [];
    Object.keys(CONSTS.STATIC_RESOURCES_EXTNAME_MAP).forEach(key => {
      ret.push(fileList.filter(item => CONSTS.STATIC_RESOURCES_EXTNAME_MAP[key].includes(item.ext)));
    });
    return ret;
  }
}

module.exports = (params) => {
  // 统一收口try-catch错误
  try {
    // 1.清空控制台、报告目录、存放项目的目录
    clearConsole();
    Fs.emptyDirectory(CONSTS.REPORT_OUTPUT_DIR);
    // 2.实例化项目检测执行
    const inspectInstance = new InspectExe(params);
    // 3.校验项目目录是否存在，存在则跳到第5步
    const prjDir = inspectInstance.getPrjDir();
    // debug模式且项目存在，直接分析项目
    if (params.debug && Fs.isDirectoryExists(prjDir)) {
      // 5.分析项目
      inspectInstance.analysePrj(prjDir);
      return;
    }
    // 4.下载项目
    // 非debug模式，清空存放项目的目录，保证每次重新拉取最新的项目分支
    Fs.emptyDirectory(prjDir);
    inspectInstance.downloadPrj(prjDir, () => {
      // 5.分析项目
      inspectInstance.analysePrj(prjDir);
    });
    // 返回项目检测执行实例对象
    return inspectInstance;
  } catch (e) {
    console.error(e);
    // 可以加上错误监控处理逻辑 - TODO
    process.exit(0);
  }
};
